import { toRaw } from 'vue'
import { format as dateFnsFormat, isValid } from 'date-fns'
import { stringify, parse } from 'query-string'
import { cloneDeep, find, isNil, isEqual, keyBy, values } from 'lodash-es'

export function noop() {}

export function polySymbol(key) {
  return (typeof Symbol === 'function' &&
    typeof Symbol.toStringTag === 'symbol')
      ? Symbol('[inecho]: ' + key)
      : '[inecho]: ' + key
}

export function getComponents(imports) {
  const components = {}

  // user/:id -> user/:Id.vue -> named route start with :
  Object.keys(imports).forEach(path => {
    const uri = path
      // clean path
      .replace(/\.\.\/\.\.\/pages|\.vue|\/Index/g, '')
      // camel case to kebab case
      .replace(/(?=[a-z])([A-Z])/g, m => '-' + m.toLowerCase())
      .replace(/\/\:?([A-Z])/g, m => m.toLowerCase())

    if (uri) {
      components[
        uri.replace(/_/g, '')
      ] = imports[path]
    }
  })

  return components
}

export function getRouteName(path) {
  if (typeof path !== 'string') return path

  return path
    .replace(/[:]/g, '')
    .replace(/[/-]+(\w)/g, (a, m) => m.toUpperCase())
    .replace(/^\w/g, m => m.toUpperCase())
}

export function getValue(o, key) {
  return key
    .replace(/\.+/g, '.')
    .split('.')
    .reduce((p, n) => {
      const steps = []

      if (typeof n === 'string') {
        n = n.replace(/\[(\d+)\]/g, (str, match) => {
          steps.push(match)
          return ''
        })

        try {
          p = p[n]
        } catch (err) {}
      }

      while (steps.length) {
        p = p || {}
        p = p[steps.shift()]
      }

      return p
    }, o)
}

export function cleanObject(o) {
  const keys = Object.keys(o)
  const copy = cloneDeep(o)

  for (let i = 0; i < keys.length; i += 1) {
    const key = keys[i]

    if (copy[key] === null || copy[key] === '') {
      delete copy[key]
    }
  }

  return copy
}

export function compare(from, to, pk = 'id') {
  const _insert = []
  const _update = []
  const _delete = keyBy(from, pk)

  for (let i = 0; i < to.length; i += 1) {
    const prop = to[i]

    if (prop[pk]) {
      if (!isEqual(_delete[prop[pk]], prop)) {
        _update.push(prop)
      }
      delete _delete[prop[pk]]
    } else {
      _insert.push(prop)
    }
  }

  return {
    _insert,
    _update,
    _delete: values(_delete)
  }
}

export function getTimestamptz(utc) {
  if (!utc) return utc

  const date = new Date(utc)

  if (isValid(date)) {
    const timestamp = date.getTime()
    const timestamptz = timestamp - date.getTimezoneOffset() * 6e4

    return timestamptz
  }
}

export function format(value, s = 'yyyy-MM-dd HH:mm:ss') {
  const timestamptz = getTimestamptz(value)

  if (timestamptz) {
    const date = new Date(timestamptz)

    return dateFnsFormat(date, s)
  }
}

export function toQuery(raw, queryOptions = []) {
  const query = {}
  const o = toRaw(raw)

  Object.keys(o).forEach(key => {
    const v = o[key]
    const option = find(queryOptions, ['key', key]) || {}

    let value

    if (Array.isArray(v)) {
      if (['range', 'daterange', 'datetimerange'].indexOf(option.type) > -1) {
        const [gt, lt] = v
        const xfmr = option.type === 'range'
          ? v => Number(v)
          : (v, isNextDay = false) => dateFnsFormat(v + 24 * 60 * 60 * 1e3 * isNextDay, 'yyyy-MM-dd HH:mm:ss')

        if (!isNil(gt)) {
          value = value || {}
          value['_gt'] = xfmr(gt)
        }
        if (!isNil(lt)) {
          value = value || {}
          value['_lt'] = xfmr(lt, true)
        }
      } else if (v.length) {
        value = { _in: v }
      }
    } else {
      if (v === 'null') {
        value = { _is_null: true }
      } else if (!isNil(v)) {
        value = queryOptions.type
          ? { _eq: v }
          : { _like: `%${v}%` }
      }
    }

    if (value) {
      query[key] = JSON.stringify(value)
    }
  })

  return stringify(query)
}

// TODO
function ifDate(v, isNextDay = false) {
  const date = new Date(v)

  if (isValid(date)) {
    return date.valueOf() - 24 * 60 * 60 * 1e3 * isNextDay
  }

  return v
}

export function toVars(s) {
  const clone = typeof s === 'string'
    ? parse(s)
    : cloneDeep(s)

  const json = {}

  Object.keys(clone).forEach(key => {
    try {
      json[key] = JSON.parse(clone[key])
    } catch (err) {}
  })

  return json
}

export function toJson(s) {
  const clone = typeof s === 'string'
    ? parse(s)
    : cloneDeep(s)

  const json = {}

  Object.keys(clone).forEach(key => {
    try {
      const o = JSON.parse(clone[key])

      if ('_eq' in o) {
        json[key] = o['_eq']
      } else if ('_is_null' in o) {
        json[key] = o['_is_null'] ? null : undefined
      } else if ('_in' in o) {
        json[key] = o['_in']
      } else {
        if ('_gt' in o) {
          // TODO datepicker invalid
          json[key] = json[key] || []
          json[key][0] = ifDate(o['_gt'])
        }
        if ('_lt' in o) {
          json[key] = json[key] || []
          json[key][1] = ifDate(o['_lt'], true)
        }
      }
    } catch (err) {}
  })

  return json
}

export function makeUpOptions(list) {
  const normalized = list.map(({ id, name, parent_id }) => {
    return { label: name, value: id, parent_id }
  })
  const returning = []
  const tree = {}

  for (let i = 0; i < normalized.length; i += 1) {
    const item = normalized[i]

    if (!item.parent_id) {
      returning.push(item)
    } else {
      tree[item.parent_id] = tree[item.parent_id] || []
      tree[item.parent_id].push(item)
    }
  }

  for (let i = 0; i < normalized.length; i += 1) {
    const item = normalized[i]

    if (Array.isArray(tree[item.value]) && tree[item.value].length) {
      item.children = tree[item.value]
    }
  }

  return returning
}

export function makeUpTree(list, options = {}) {
  const {
    idKey = 'key',
    nameKey = 'title'
  } = options
  const normalized = list.map(({ id, name, parent_id, ...args }) => {
    return { [nameKey]: name, [idKey]: id, parent_id, ...args }
  })
  const returning = []
  const tree = {}

  for (let i = 0; i < normalized.length; i += 1) {
    const item = normalized[i]

    if (!item.parent_id) {
      returning.push(item)
    } else {
      tree[item.parent_id] = tree[item.parent_id] || []
      tree[item.parent_id].push(item)
    }
  }

  for (let i = 0; i < normalized.length; i += 1) {
    const item = normalized[i]

    if (Array.isArray(tree[item[idKey]]) && tree[item[idKey]].length) {
      item.children = tree[item[idKey]]
    }
  }

  return returning
}

export function findTreeNode(list, id, options = {}) {
  const {
    idKey = 'key',
  } = options

  function tree2Array(tree) {
    const array = []

    for (let i = 0; i < tree.length; i += 1) {
      const item = tree[i]

      if (item) {
        array.push(item)
      }
      if (Array.isArray(item?.children)) {
        array.push(
          ...tree2Array(item.children)
        )
      }
    }

    return array
  }

  return tree2Array(list).find(item => item[idKey] === id)
}

export function openWindow(src, name = '_blank', w = 800, h = 600) {
  const x = (window.screenX || window.screenLeft || 0) + (window.screen.width - w) / 2
  const y = (window.screenY || window.screenTop || 0) + (window.screen.height - h) / 2
  const specs = `width=${w},height=${h},top=${y},left=${x}` +
    'scrollbars=no,resizable=no,status=no,location=no,titlebar=no'

  // replace history
  const iframe = window.open(src, name, specs, true)

  if (iframe) {
    iframe.focus()
  }
}
